package com.kou.backend.service.impl.user;

import cn.hutool.core.util.CharsetUtil;
import com.kou.backend.constants.RedisKeyConstants;
import com.kou.backend.dao.UserInfoDao;
import com.kou.backend.dto.user.SaveLoginLogDTO;
import com.kou.backend.entity.UserInfoDO;
import com.kou.backend.error.CommonErrorEnum;
import com.kou.backend.exception.CommonException;
import com.kou.backend.property.BusinessDynamicProperties;
import com.kou.backend.property.JwtProperties;
import com.kou.backend.request.login.UserLoginReqDTO;
import com.kou.backend.resposne.login.UserLoginResponseDTO;
import com.kou.backend.service.UserLoginLogService;
import com.kou.backend.service.UserLoginService;
import com.kou.backend.util.JwtTokenUtils;
import com.kou.backend.util.RedisUtil;
import com.kou.backend.utils.FetchRequestInfoUtils;
import com.kou.backend.utils.MD5EncryptionUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Optional;

/**
 * @author: Chaojie.Kou
 * @since: 2023/8/30 16:11
 */
@Slf4j
@Service
public class UserLoginServiceImpl implements UserLoginService {
    @Resource
    private RedisUtil redisUtil;
    @Resource
    private UserInfoDao userInfoDao;
    @Resource
    private JwtTokenUtils jwtTokenUtils;
    @Resource
    private JwtProperties jwtProperties;
    @Resource
    private BusinessDynamicProperties businessDynamicProperties;
    @Resource
    private UserDetailsService userDetailsService;
    @Resource
    private UserLoginLogService userLoginLogService;

    @Override
    public UserLoginResponseDTO login(UserLoginReqDTO reqDTO) {
        UserLoginResponseDTO responseDTO;
        SaveLoginLogDTO loginLogDTO = new SaveLoginLogDTO();
        try {
            Integer errorFrequency = (Integer) Optional.ofNullable(redisUtil.get(RedisKeyConstants.UserLoginKey.ERROR_PASSWORD_FREQUENCY + reqDTO.getUserAccount())).orElse(0);
            if (errorFrequency >= businessDynamicProperties.getErrorPasswordFrequency()) {
                throw new CommonException(CommonErrorEnum.ERROR_USER_PASSWORD_MAX);
            }
            responseDTO = new UserLoginResponseDTO();
            //校验帐号和密码
            UserInfoDO userAccount = Optional.ofNullable(userInfoDao.selectOneByAccountEffective(reqDTO.getUserAccount(), "1"))
                    .orElseThrow(() -> new CommonException(CommonErrorEnum.ERROR_USER_PASSWORD));
            boolean verify = MD5EncryptionUtil.verify(reqDTO.getPassword(), userAccount.getPassword(), userAccount.getSalt(), CharsetUtil.UTF_8);
            if (!verify) {
                //将输入密码错误次数记录,超过5次停用帐号
                recordErrorFrequency(userAccount);
                log.warn("用户:{},密码错误", reqDTO.getUserAccount());
                throw new CommonException(CommonErrorEnum.ERROR_USER_PASSWORD);
            }

            //SecurityContextHolder上下文赋值
            UserDetails userDetails = userDetailsService.loadUserByUsername(userAccount.getUserAccount());
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, reqDTO.getPassword(), userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);

            //生成token,存入redis
            String token = jwtTokenUtils.generateToken(userAccount.getUserAccount(), userAccount.getUserName());
            redisUtil.set(jwtProperties.getKeyPrefix() + userAccount.getUserAccount(), token, jwtProperties.getExpiration());

            responseDTO.setToken(token);
            responseDTO.setUserAccount(userAccount.getUserAccount());

            //重置密码错误次数
            redisUtil.delete(Collections.singletonList(RedisKeyConstants.UserLoginKey.ERROR_PASSWORD_FREQUENCY + reqDTO.getUserAccount()));
        } catch (Exception e) {
            loginLogDTO.setStatus("0");
            log.error("用户登录异常", e);
            throw e;
        } finally {
            buildLoginLogDTO(reqDTO, loginLogDTO);
            //保存登录日志
            userLoginLogService.saveLog(loginLogDTO);
        }

        return responseDTO;
    }

    private static void buildLoginLogDTO(UserLoginReqDTO reqDTO, SaveLoginLogDTO loginLogDTO) {
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        loginLogDTO.setUserAccount(reqDTO.getUserAccount());
        loginLogDTO.setIpAddress(FetchRequestInfoUtils.getIpAddress(request));
        loginLogDTO.setBrowserType(FetchRequestInfoUtils.browserName(request));
        loginLogDTO.setSystemType(FetchRequestInfoUtils.osName(request));
    }

    /**
     * 记录用户密码错误次数
     *
     * @param userAccount 用户帐号
     */
    private void recordErrorFrequency(UserInfoDO userAccount) {
        Long incrBy = redisUtil.incrBy(RedisKeyConstants.UserLoginKey.ERROR_PASSWORD_FREQUENCY + userAccount.getUserAccount(), 1L);
        if (incrBy >= businessDynamicProperties.getErrorPasswordFrequency()) {
            userAccount.setStatus("0");
            userAccount.setUpdateTime(LocalDateTime.now());
            userInfoDao.updateById(userAccount);
        }
    }
}
